// Google test includes
#include <gtest/gtest.h>
#include <gmock/gmock.h>

// Tested file
#include "Printers/PostfixPrinter.h"

// Mocks and fakes
#include "MockNode.h"

using namespace testing;

typedef PostfixPrinter<TestNode> TestedPrinter;

TEST(PostfixPrinterTest, VisitAtom)
{
	// Prepare nodes fakes
	StrictMock<TestNodeAtom> node("test");

	// Force printing or node and check print result
    EXPECT_EQ("test", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitNot)
{
	// Prepare nodes fakes
	StrictMock<TestNodeNot> node;
	StrictMock<TestNodeAtom> child0("test0");

	// Set up expectations
	EXPECT_CALL(node, getChildrenCount()).WillRepeatedly(Return(0));
	EXPECT_CALL(node, getChild(0)).WillOnce(Return(&child0));

	// Force printing or node and check print result
    EXPECT_EQ("test0~", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitAnd_SingleChild)
{
	// Prepare nodes fakes
	StrictMock<TestNodeAnd> node;
	StrictMock<TestNodeAtom> child0("test0");

	// Set up expectations
	EXPECT_CALL(node, getChildrenCount()).WillRepeatedly(Return(1));
	EXPECT_CALL(node, getChild(0)).WillOnce(Return(&child0));

	// Force printing or node and check print result
    EXPECT_EQ("test0", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitAnd_MultipleChildren)
{
	// Prepare nodes fakes
	StrictMock<TestNodeAnd> node;
	StrictMock<TestNodeAtom> child0("test0");
	StrictMock<TestNodeAtom> child1("test1");
	StrictMock<TestNodeAtom> child2("test2");

	// Set up expectations
	EXPECT_CALL(node, getChildrenCount()).WillRepeatedly(Return(3));
	EXPECT_CALL(node, getChild(0)).WillOnce(Return(&child0));
	EXPECT_CALL(node, getChild(1)).WillOnce(Return(&child1));
	EXPECT_CALL(node, getChild(2)).WillOnce(Return(&child2));

	// Force printing or node and check print result
    EXPECT_EQ("test0test1&test2&", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitOr_SingleChild)
{
	// Prepare nodes fakes
	StrictMock<TestNodeOr> node;
	StrictMock<TestNodeAtom> child0("test0");

	// Set up expectations
	EXPECT_CALL(node, getChildrenCount()).WillRepeatedly(Return(1));
	EXPECT_CALL(node, getChild(0)).WillOnce(Return(&child0));

	// Force printing or node and check print result
    EXPECT_EQ("test0", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitOr_MultipleChildren)
{
	// Prepare nodes fakes
	StrictMock<TestNodeOr> node;
	StrictMock<TestNodeAtom> child0("test0");
	StrictMock<TestNodeAtom> child1("test1");
	StrictMock<TestNodeAtom> child2("test2");

	// Set up expectations
	EXPECT_CALL(node, getChildrenCount()).WillRepeatedly(Return(3));
	EXPECT_CALL(node, getChild(0)).WillOnce(Return(&child0));
	EXPECT_CALL(node, getChild(1)).WillOnce(Return(&child1));
	EXPECT_CALL(node, getChild(2)).WillOnce(Return(&child2));

	// Force printing or node and check print result
    EXPECT_EQ("test0test1|test2|", TestedPrinter::print(&node));
}

TEST(PostfixPrinterTest, VisitComplex)
{
	// Prepare nodes fakes and set up expectations
	StrictMock<TestNodeAtom> child0("A");
	StrictMock<TestNodeAtom> child1("B");
	StrictMock<TestNodeAtom> child2("C");
	StrictMock<TestNodeAtom> child3("D");

	StrictMock<TestNodeOr> orNode;
	EXPECT_CALL(orNode, getChildrenCount()).WillRepeatedly(Return(2));
	EXPECT_CALL(orNode, getChild(0)).WillOnce(Return(&child0));
	EXPECT_CALL(orNode, getChild(1)).WillOnce(Return(&child1));

	StrictMock<TestNodeNot> notNode;
	EXPECT_CALL(notNode, getChildrenCount()).WillRepeatedly(Return(1));
	EXPECT_CALL(notNode, getChild(0)).WillOnce(Return(&child2));

	StrictMock<TestNodeAnd> root;
	EXPECT_CALL(root, getChildrenCount()).WillRepeatedly(Return(3));
	EXPECT_CALL(root, getChild(0)).WillOnce(Return(&orNode));
	EXPECT_CALL(root, getChild(1)).WillOnce(Return(&notNode));
	EXPECT_CALL(root, getChild(2)).WillOnce(Return(&child3));

	// Force printing or node and check print result
    EXPECT_EQ("AB|C~&D&", TestedPrinter::print(&root));
}

TEST(PostfixPrinterTest, AllowsReentrance)
{
	// Prepare nodes fakes
	StrictMock<TestNodeAtom> node("test0");

	// Process for first time
    EXPECT_EQ("test0", TestedPrinter::print(&node));

    // Perform processing once again
    EXPECT_EQ("test0", TestedPrinter::print(&node));
}

